﻿#define _CRT_SECURE_NO_WARNINGS 1
#define NDEBUG

#include <stdio.h>
#include <string.h>
#include <assert.h>

int temp1 = 20;
int temp2 = 30;

void swap(int x, int y)
{
	int z = 0;
	z = y;
	y = x;
	x = z;
}
void test()
{
	int n = 10;
	int m = 20;
	const int* const p = &n;
	//*p = 20; 
	//p = &m; 
}
size_t my_strlen(char* s)//数组的名字其实是数组的首地址
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}
size_t my_strlen2(const char* s)
{
	/*提高函数的健壮性*/
	size_t count=0;
	assert(s != NULL);
	while(*s)
	{
		count++;
		s++;
	}
	return count;
}

int main(void)
{
	int a = 20;
	int* pa = &a;//是个用来存放地址的变量
	*pa = 200;
	/*
		变量创建的本质其实是在内存中申请空间
		变量的名字仅仅是给程序员看的，编译器不看名字，编译器是通过地址找内存单元的
		每一个字节都有编号
		&a:拿到四个字节的第一个字节的地址（低位）
		int *:*表示是指针变量，int 指针指向的变量的类型
		*:解引用操作符（间接访问操作）
		指针变量的⼤⼩取决于地址的⼤⼩
		32位平台下地址是32个bit位（即4个字节）
		64位平台下地址是64个bit位（即8个字节）
		指针的类型决定了，对指针解引⽤的时候有多⼤的权限（⼀次能操作⼏个字节）。
		⽐如： char* 的指针解引⽤就只能访问⼀个字节，⽽ int* 的指针的解引⽤就能访问四个字节。
	*/
	{
		/*
			指针的类型决定了指针向前或者向后⾛⼀步有多⼤（距离）。
			void * 类型，可以理解为⽆具体类型的指针（或者叫泛型指针），这种类型的指针可以⽤来接受任意类型地址。
			但是也有局限性， void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。
			void* 类型的指针是使⽤在函数参数的部分，⽤来接收不同类型数据的地址，这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据
		*/
		int a = 20;
		int* pa = &a;
		char* pc = &a;
		printf("&a=%p\n", &a);
		printf("pa=%p\n", pa);
		printf("pc=%p\n", pc);

		printf("&a+1=%p\n", &a+1);
		printf("pa+1=%p\n", pa+1);
		printf("pc+1=%p\n", pc+1);
		
	}
	{
		/*
			const如果放在*的左边，修饰的是指针指向的内容，保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
•			const如果放在*的右边，修饰的是指针变量本⾝，保证了指针变量的内容不能修改，但是指针指向的内容，可以通过指针改变
		*/
		const int n = 0;
		printf("n = %d\n", n);
		int* p = &n;
		*p = 20;
		printf("n = %d\n", n);
	}
	{	//指针+- 整数
		int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
		int siz = sizeof(arr) / sizeof(arr[0]);
		int* p = &arr[0];
		for (int i = 0; i < siz; i++)
		{
			//printf("%d ", *(p+1));
			//printf("%d ", *(p++));
			printf("%d ", *(p + i));
			//p++;			//赋值语句
			//p--:实现数组的逆序排列
		}
		//指针减指针的绝对值是两个之间的元素个数，前提是指向的同一个空间
		printf("\n%zd\n", my_strlen("abc"));
		//指针+指针：毫无意义
	}
	{	//指针的关系运算
		int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
		int* p = &arr[0];
		int sz = sizeof(arr) / sizeof(arr[0]);
		while (p < arr + sz) //指针的⼤⼩⽐较
		{
			printf("%d ", *p);
			p++;
		}
	}
	{	/*
			野指针就是指针指向的位置是不可知的（随机的、不正确的、没有明确限制的）
			1.指针未初始化
				int* p;
				*p = 0;
			2.指针越界访问
			3.指针指向的空间释放
		*/
		/*
			如何避免野指针
			1.指针初始化：如果明确知道指针指向哪⾥就直接赋值地址，如果不知道指针应该指向哪⾥，可以给指针赋值NULL.
			NULL 是C语⾔中定义的⼀个标识符常量，值是0，0也是地址，这个地址是⽆法使⽤的，读写该地址会报错。
			2.⼩⼼指针越界⼀个程序向内存申请了哪些空间，通过指针也就只能访问哪些空间，不能超出范围访问，超出了就是
			越界访问。
			3.指针变量不再使⽤时，及时置NULL，指针使⽤之前检查有效性
			4.避免返回局部变量的地址
		*/
		/*
			assert.h 头⽂件定义了宏 assert() ，⽤于在运⾏时确保程序符合指定条件，如果不符合，就报错终⽌运⾏。这个宏常常被称为“断⾔”
			如果已经确认程序没有问题，不需要再做断⾔，就在 #include <assert.h> 语句的前⾯，定义⼀个宏 NDEBUG 。
			#define NDEBUG
		*/
		int* p = NULL;
		assert(p != NULL);
	}
	{
		/*
			写⼀个函数，交换两个整型变量的值（强调用函数实现）
			传值调⽤：只传了值，没传地址
			实参传递给形参的时候，形参会单独创建⼀份临时空间来接收实参，对形参的修改不影响实参。
			传址调⽤
			可以让函数和主调函数之间建⽴真正的联系，在函数内部可以修改主调函数中的变量；
			所以未来函数中只是需要主调函数中的变量值来实现计算，就可以采⽤传值调⽤。
			如果函数内部要修改主调函数中的变量的值，就需要传址调⽤。
		*/
		//即使是全局变量也不能交换
		printf("交换前temp1=%d temp2=%d\n", temp1, temp2);
		swap(temp1, temp2);
		printf("交换后temp1=%d temp2=%d\n", temp1, temp2);
	}
	return 0;
}
